#pragma once

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

#include <memory>

#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"

namespace SocketModule
{
    using namespace logModule;

    const int defaultBacklog = 8;
    const int defaultFd = -1;

    // 基类方法模式
    // 基类 socket 大部分方法都是纯虚函数方法
    class Socket
    {
    public:

        virtual ~Socket() = default;
        virtual void socket() = 0;
        virtual void bind(uint16_t) = 0;
        virtual void listen(int) = 0;
        virtual std::shared_ptr<Socket> accept(InetAddr&) = 0;
        virtual int acceptFd(InetAddr&) = 0;
        virtual void close() = 0;
        virtual int recv(std::string&) = 0;
        virtual int send(const std::string&) = 0;
        virtual int connect(const std::string&, uint16_t) = 0;
        virtual int getFd() = 0;

        void buildTcpSocketMethod(uint16_t port, int backlog = defaultBacklog)
        {
            socket();
            bind(port);
            listen(backlog);
        }

        void buildTcpClientSocketMethod()
        {
            socket();
        }
    };
    
    class TcpSocket : public Socket
    {
    public:

        TcpSocket()
            :_sockfd(defaultFd)
        {
            ;
        }

        TcpSocket(int fd)
            :_sockfd(fd)
        {
            ;
        }

        void close() override
        {
            if (_sockfd >= 0)
            {
                ::close(_sockfd);
            }
        }

        int getFd() override
        {
            return _sockfd;
        }

        int acceptFd(InetAddr& client) override
        {
            struct sockaddr_in aim;
            socklen_t len = sizeof(aim);
            
            int sockfd = ::accept(_sockfd, (struct sockaddr*)&aim, &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept failure";
                return -1;
            }
            LOG(LogLevel::DEBUG) << "accept success";

            return sockfd;
        }

        std::shared_ptr<Socket> accept(InetAddr& client) override
        {
            struct sockaddr_in aim;
            socklen_t len = sizeof(aim);
            
            int sockfd = ::accept(_sockfd, (struct sockaddr*)&aim, &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept failure";
                return nullptr;
            }
            LOG(LogLevel::DEBUG) << "accept success";

            client.setInetAddr(aim);

            return std::make_shared<TcpSocket>(sockfd);
        }

        void listen(int backlog) override
        {
            int returnNum = ::listen(_sockfd, backlog);
            if (returnNum < 0)
            {
                LOG(LogLevel::FATAL) << "listen failure";
                exit(LISTEN_ERR);
            }
            LOG(LogLevel::DEBUG) << "listen success";
        }

        void bind(uint16_t port) override
        {
            InetAddr local(port);
            int returnNum = ::bind(_sockfd, local.getAddrPtr(), local.getAddrLen());
            if (returnNum < 0)
            {
                LOG(LogLevel::FATAL) << "bind failure";
                exit(BIND_ERR);
            }
            LOG(LogLevel::DEBUG) << "bind success";
        }

        void socket() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "socket failure";
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::DEBUG) << "socket success";
        }

        int recv(std::string& info) override
        {
            char buf[1024 * 64];
            ssize_t len = ::recv(_sockfd, buf, sizeof(buf) - 1, 0);
            if (len > 0)
            {
                buf[len] = 0;
                info += buf;
            }
            return len;
        }

        int send(const std::string& str) override
        {
            return ::send(_sockfd, str.c_str(), str.size(), 0);
        }

        int connect(const std::string& ip, uint16_t port) override
        {
            InetAddr server(ip, port);
            return ::connect(_sockfd, server.getAddrPtr(), server.getAddrLen());
        }

        ~TcpSocket()
        {
            ;
        }

    private:
        
        int _sockfd;
    };
}

